#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>

// Declaracion de Constantes
//----------------------------------------------------------------------
#define MAX_ESPACIO 10
#define MAX_THREAD 100
//----------------------------------------------------------------------

//struct posicion
typedef struct posicion{
	int posx;
	int posy;
	
}Posicion;

// Declaracion de variables globales
//----------------------------------------------------------------------
int espacio[2][MAX_ESPACIO][MAX_ESPACIO]; // espacio celular
int largo_espacio;						  // largo del espacio celular
int num_celulas;						  // indica el numero de celulas generadas						
int num_generaciones;					  // numero de generaciones
int iteraciones;						  // cuenta el numero de iteraciones
int llegaron = 0;							  // contador de llegada de thread
int actual = 0;							  // indica generacion actual
int sgte = 1;							  // indica la generacion siguiente

//semaforos
sem_t s, mutex, mutex3;							  // semaforos de la barrera
sem_t mutex1, escribe, mutex2;
//----------------------------------------------------------------------

// Declaracion de funciones
//----------------------------------------------------------------------
void Imprime_Tablero();
int cuentaVecinos(int posy, int posx);
void nuevaCelula(int posy, int posx);
void barrera();
void *thread();
void init(sem_t *sem, int valor_inicial);
void p(sem_t *sem);
void v(sem_t *sem);
void limpiaEspacio();
void cambiaEspacio();
//----------------------------------------------------------------------

// funcion principal
int main(int argc, char *argv[]) {
	
	pthread_t celulas[MAX_THREAD];
	FILE *archivo;
	int i=0,j=0, count=0;
	char caracter[2];
	Posicion pos;
	void *status;
	
	if (argc != 4) {
      printf("Uso correcto: %s Num-Espacio nombre-archivo  num-generaciones\n", argv[0]);
      exit(EXIT_FAILURE);
    }
 
	archivo = fopen(argv[2],"r");
	if (archivo == NULL)
		exit(EXIT_FAILURE);
 
	largo_espacio = atoi(argv[1]);
	num_celulas = largo_espacio*largo_espacio;
	num_generaciones = atoi(argv[3]);
	// cargo el archivo en el espacio celular
	
	for(j = 1; j <= largo_espacio; j++ ){
		for(i = 1; i <= largo_espacio; i++){
			fgets(caracter,3,archivo);
			espacio[actual][j][i] = atoi(caracter);
		}
	}
	printf("Tablero Inicial\n");
	Imprime_Tablero();
	// inicializacion de los semaforos
	init(&mutex, 1);
	init(&mutex1, 1);
	init(&mutex2, 1);
	init(&mutex3,1);
	init(&escribe, 1);
	init(&s, num_celulas);
	// generacion de threads para el comienzo de las iteraciones
	
	for(j=1; j <= largo_espacio; j++){
		for(i = 1; i <= largo_espacio; i++){
			// creo el thread para una celula
			pos.posx = i;
			pos.posy = j;
			//printf("creando thread %d\n",count);
			if (pthread_create(&celulas[count], NULL, thread, (void *)&pos)) {
				printf("Error creando thread productor\n");
				exit(EXIT_FAILURE);
			}
			count++;
		}
		printf("\n");
	}

   
   for(j=0;j<num_celulas;j++) {
      pthread_join(celulas[j], &status);
   }
   printf("Todos los threads terminaron!\n");
}

// Imprime el tablero del espacio celular
//----------------------------------------------------------------------
void Imprime_Tablero(){
	int i,j;
	printf("\n\n Iteracion %d\n",iteraciones++);
	for(j=1; j <= largo_espacio; j++){
		for(i = 1; i <= largo_espacio; i++){
			printf("%d ",espacio[actual][j][i]);
		}
		printf("\n");
	}
}
//----------------------------------------------------------------------


// Cuenta el numero de vecinos 
// que tiene la celula y lo retorna
//----------------------------------------------------------------------
int cuentaVecinos(int posy, int posx){
	int contador=0;
	if(espacio[actual][posy-1][posx-1] == 1)contador++;
	if(espacio[actual][posy-1][posx] == 1)contador++;
	if(espacio[actual][posy-1][posx+1] == 1)contador++;
	if(espacio[actual][posy][posx-1] == 1)contador++;
	if(espacio[actual][posy][posx+1] == 1)contador++;
	if(espacio[actual][posy+1][posx-1] == 1)contador++;
	if(espacio[actual][posy+1][posx] == 1)contador++;
	if(espacio[actual][posy+1][posx+1] == 1)contador++;
	return contador;
	
}
//----------------------------------------------------------------------

//escribe en en espacio la celula en la nueva generacion
//dependiendo del numero de vecinos es si la celula
// esta viva o muerta.
//----------------------------------------------------------------------
void nuevaCelula(int posy, int posx){
	 
	if((cuentaVecinos(posy,posx) == 2 || cuentaVecinos(posy,posx) == 3) && espacio[actual][posy][posx] == 1){
		//printf("\nviva %d-%d\n",posy,posx);
		espacio[sgte][posy][posx] = 1;
		}
	
	if((cuentaVecinos(posy,posx) >= 4 || cuentaVecinos(posy,posx) <= 1) && espacio[actual][posy][posx] == 1){
		//printf("\nmuerta %d-%d\n",posy,posx);
		espacio[sgte][posy][posx] = 0;
		
	}
	
	if(cuentaVecinos(posy,posx) == 3 && espacio[actual][posy][posx] == 0) {
		//printf("\nviva %d-%d\n",posy,posx);
		espacio[sgte][posy][posx] = 1;

	}
}
//----------------------------------------------------------------------

//Implementa una barrera con semaforos 
// se usa para que los threads esperen 
// en un punto para poder seguir avanzando
//----------------------------------------------------------------------
void barrera(int iteracion){
	int i=0;
	p(&mutex);
	llegaron++;
	if(llegaron < num_celulas){
		v(&mutex);
		p(&s);
	}
	else{
		printf("\nllegaron todos %d \n",llegaron);
		llegaron = 0;
		v(&mutex);
		cambiaEspacio();
		limpiaEspacio();
		Imprime_Tablero();
		
		for( i=0; i < num_celulas-1; i++)v(&s);
		
	}
	
	
	
}
//----------------------------------------------------------------------

//inicializacion de los semaforos
//----------------------------------------------------------------------
void init(sem_t *sem, int valor_inicial) {
   if (sem_init(sem, 0, valor_inicial)) {
      printf("Error inicializando semaforo\n");
      exit(EXIT_FAILURE);
   }
}
// funcion p para bloquear el proceso
//----------------------------------------------------------------------
void p(sem_t *sem) {
   if (sem_wait(sem)) {
      printf("Error fatal haciendo sem_wait()\n");
      exit(EXIT_FAILURE);
   }
}
// funcion v para desbloquear el proceso
//----------------------------------------------------------------------
void v(sem_t *sem) {
   if (sem_post(sem)) {
      printf("Error fatal haciendo sem_post()\n");
      exit(EXIT_FAILURE);
   }
}
//----------------------------------------------------------------------

//Threads crea la funcion 
//----------------------------------------------------------------------
void *thread(void *pos){
	Posicion *po = pos;
	int i=0,j;
	for (i = 0; i < num_generaciones; i++) {
      nuevaCelula(po->posy, po->posx);
      barrera(i);
      sleep(1);
       
   }
   pthread_exit(NULL);
}
//----------------------------------------------------------------------

//limpia el espacio celular a ocupar
//----------------------------------------------------------------------
void limpiaEspacio(){
	int i,j;
	printf("borra el anterior\n");
	p(&mutex1);
	for(j=1; j <= largo_espacio; j++){
		for(i = 1; i <= largo_espacio; i++){
			espacio[sgte][j][i] = 0;
		}
	}
	v(&mutex1);
}
//----------------------------------------------------------------------

//
//----------------------------------------------------------------------
void cambiaEspacio(){
	int i,j;
	
	p(&mutex2);
	for(j=1; j < num_celulas; j++){
		for(i=1; i < num_celulas; i++){
			espacio[actual][j][i] = espacio[sgte][j][i];
		}
	}
		v(&mutex2);
	
}
//----------------------------------------------------------------------
